package cn.edu.hqu.pre;

import cn.hutool.core.util.NumberUtil;
import cn.hutool.poi.excel.ExcelReader;
import cn.hutool.poi.excel.ExcelUtil;
import org.uma.jmetal.component.algorithm.EvolutionaryAlgorithm;
import org.uma.jmetal.component.catalogue.common.evaluation.impl.SequentialEvaluation;
import org.uma.jmetal.component.catalogue.common.solutionscreation.impl.RandomSolutionsCreation;
import org.uma.jmetal.component.catalogue.common.termination.impl.TerminationByEvaluations;
import org.uma.jmetal.component.catalogue.ea.replacement.impl.MuPlusLambdaReplacement;
import org.uma.jmetal.component.catalogue.ea.selection.impl.NaryTournamentSelection;
import org.uma.jmetal.component.catalogue.ea.variation.impl.CrossoverAndMutationVariation;
import org.uma.jmetal.operator.crossover.impl.PMXCrossover;
import org.uma.jmetal.operator.mutation.impl.PermutationSwapMutation;
import org.uma.jmetal.solution.permutationsolution.PermutationSolution;
import org.uma.jmetal.util.JMetalLogger;
import org.uma.jmetal.util.comparator.MultiComparator;
import org.uma.jmetal.util.comparator.ObjectiveComparator;
import org.uma.jmetal.util.comparator.constraintcomparator.impl.NumberOfViolatedConstraintsComparator;
import org.uma.jmetal.util.comparator.constraintcomparator.impl.OverallConstraintViolationDegreeComparator;
import org.uma.jmetal.util.fileoutput.SolutionListOutput;
import org.uma.jmetal.util.fileoutput.impl.DefaultFileOutputContext;
import org.uma.jmetal.util.pseudorandom.JMetalRandom;

import java.util.List;
import java.util.Map;
import java.util.Objects;

/**
 * 基于JMetal求解“三日游旅游线路”
 *
 * @author 洪佳森
 * @date 2023-12-11
 */
public class ThreeDayTripRouteMain {

    public static void main(String[] args) {
        boolean flag = true;
        while (flag && !CodeHelp.codeMap.isEmpty()) {
            System.out.println(CodeHelp.codeMap);
            ThreeDayTripRouteProblem problem = new ThreeDayTripRouteProblem(CodeHelp.codeMap.size());
            problem.setDistanceMatrix(readData1(CodeHelp.codeMap));
            problem.setTravelTimeMatrix(readData2(CodeHelp.codeMap));
            problem.setPlayMatrix(readData3(CodeHelp.codeMap));
            problem.setRankMatrix(readData4(problem, CodeHelp.codeMap));

            int populationSize = 100;
            var createInitialPopulation = new RandomSolutionsCreation<>(problem, populationSize);

            var comparator = new MultiComparator<PermutationSolution<Integer>>(
                    List.of(new NumberOfViolatedConstraintsComparator<>(),
                            new ObjectiveComparator<>(0), new ObjectiveComparator<>(1)));

            var replacement = new MuPlusLambdaReplacement<>(comparator);

            var crossover = new PMXCrossover(0.9);

            double mutationProbability = 0.2;
            var mutation = new PermutationSwapMutation<Integer>(mutationProbability);
            var variation = new CrossoverAndMutationVariation<>(populationSize, crossover, mutation);

            var selection = new NaryTournamentSelection<PermutationSolution<Integer>>(2
                            , variation.getMatingPoolSize(), new ObjectiveComparator<>(0));

            var termination = new TerminationByEvaluations(50000);

            var evaluation = new SequentialEvaluation<>(problem);

            EvolutionaryAlgorithm<PermutationSolution<Integer>> geneticAlgorithm = new EvolutionaryAlgorithm<>(
                    "GGA",
                    createInitialPopulation, evaluation, termination, selection, variation, replacement) {

                @Override
                public void updateProgress() {
                    PermutationSolution<Integer> bestFitnessSolution = population().stream()
                            .min(new MultiComparator<>(List.of(new OverallConstraintViolationDegreeComparator<>(),
                                    new ObjectiveComparator<>(0)))).get();
                    attributes().put("BEST_SOLUTION", bestFitnessSolution);

                    super.updateProgress();
                }
            };

            geneticAlgorithm.run();

            List<PermutationSolution<Integer>> population = geneticAlgorithm.result();
            JMetalLogger.logger.info("Total execution time : " + geneticAlgorithm.totalComputingTime() + "ms");
            JMetalLogger.logger.info("Number of evaluations: " + geneticAlgorithm.numberOfEvaluations());
            JMetalLogger.logger.info("Best found solution: " + population.get(0).objectives()[0]);

            double timeConstraint = population.get(0).constraints()[0];
            JMetalLogger.logger.info("Time constraint found solution: " + timeConstraint);

            if (timeConstraint >= 0) {
                new SolutionListOutput(population)
                        .setVarFileOutputContext(new DefaultFileOutputContext(problem.name() + "VAR.tsv"))
                        .setFunFileOutputContext(new DefaultFileOutputContext(problem.name() + "FUN.tsv"))
                        .print();

                JMetalLogger.logger.info("Random seed: " + JMetalRandom.getInstance().getSeed());
                JMetalLogger.logger.info("Objectives values have been written to file " + problem.name() + "FUN.tsv");
                JMetalLogger.logger.info("Variables values have been written to file " + problem.name() + "VAR.tsv");

                flag = false;
            } else {
                Double min = problem.getRankMap().values().stream().min(Double::compareTo).get();
                for (Map.Entry<String, Double> entry : problem.getRankMap().entrySet()) {
                    if (Objects.equals(entry.getValue(), min)) {
                        Integer code = CodeHelp.codeMap.get(entry.getKey());
                        CodeHelp.codeMap.forEach((k, v) -> {
                            if (v > code) {
                                CodeHelp.codeMap.put(k, v - 1);
                            }
                        });
                        CodeHelp.codeMap.remove(entry.getKey());
                        break;
                    }
                }
            }
        }
    }

    public static double[][] readData1(Map<String, Integer> codeMap) {
        double [][] matrix = new double[codeMap.size()][codeMap.size()];

        ExcelReader reader =  ExcelUtil.getReader("F:\\test-project\\hqu-route-design\\src\\main\\resources\\data.xlsx", 0);
        List<Map<String, Object>> data = reader.read(0, 1, reader.getRowCount());

        for (int i = 0; i < data.size(); i++) {
            String xs = data.get(i).get("遗产点i").toString();
            String ys = data.get(i).get("遗产点j").toString();
            if (codeMap.containsKey(xs) && codeMap.containsKey(ys)) {
                double d = NumberUtil.parseDouble(data.get(i).get("最快的路程（千米）").toString());

                Integer x = codeMap.get(xs);
                Integer y = codeMap.get(ys);
                matrix[x][y] = d;
                matrix[y][x] = d;
            }
        }

        return matrix;
    }

    public static double[][] readData2(Map<String, Integer> codeMap) {
        double [][] matrix = new double[codeMap.size()][codeMap.size()];

        ExcelReader reader =  ExcelUtil.getReader("F:\\test-project\\hqu-route-design\\src\\main\\resources\\data.xlsx", 0);
        List<Map<String, Object>> data = reader.read(0, 1, reader.getRowCount());

        for (int i = 0; i < data.size(); i++) {
            String xs = data.get(i).get("遗产点i").toString();
            String ys = data.get(i).get("遗产点j").toString();
            if (codeMap.containsKey(xs) && codeMap.containsKey(ys)) {
                double d = NumberUtil.parseDouble(data.get(i).get("旅途时间（分钟）").toString());

                Integer x = codeMap.get(xs);
                Integer y = codeMap.get(ys);
                matrix[x][y] = d;
                matrix[y][x] = d;
            }
        }

        return matrix;
    }

    public static double[] readData3(Map<String, Integer> codeMap) {
        double [] matrix = new double[codeMap.size()];

        ExcelReader reader =  ExcelUtil.getReader("F:\\test-project\\hqu-route-design\\src\\main\\resources\\data.xlsx", 1);
        List<Map<String, Object>> data = reader.read(0, 1, reader.getRowCount());

        for (int i = 0; i < data.size(); i++) {
            String xs = data.get(i).get("遗产点").toString();
            if (codeMap.containsKey(xs)) {
                double d = NumberUtil.parseDouble(data.get(i).get("推荐游玩时间").toString());

                Integer x = codeMap.get(xs);
                matrix[x] = d;
            }
        }

        return matrix;
    }

    public static double[] readData4(ThreeDayTripRouteProblem problem, Map<String, Integer> codeMap) {
        double [] matrix = new double[codeMap.size()];

        ExcelReader reader =  ExcelUtil.getReader("F:\\test-project\\hqu-route-design\\src\\main\\resources\\data.xlsx", 1);
        List<Map<String, Object>> data = reader.read(0, 1, reader.getRowCount());

        for (int i = 0; i < data.size(); i++) {
            String xs = data.get(i).get("遗产点").toString();
            if (codeMap.containsKey(xs)) {
                double d = NumberUtil.parseDouble(data.get(i).get("热度").toString());

                Integer x = codeMap.get(xs);
                matrix[x] = d;
                problem.setRank(xs, d);
            }
        }

        return matrix;
    }
}
